package com.ruoyi.handler.socket;

import com.ruoyi.common.constant.AlarmLevel;
import com.ruoyi.common.constant.AlarmResolveStatus;
import com.ruoyi.common.constant.AlarmType;
import com.ruoyi.common.constant.OperationType;
import com.ruoyi.common.constant.socket.DeviceTypeContant;
import com.ruoyi.common.constant.socket.MessageType;
import com.ruoyi.common.core.domain.entity.socket.bean.*;
import com.ruoyi.common.core.domain.entity.system.*;
import com.ruoyi.common.core.domain.entity.wsbean.JishiOrderEndChargeMsg;
import com.ruoyi.common.core.domain.entity.wsbean.OrderBeginChargeMsg;
import com.ruoyi.common.core.domain.entity.wsbean.TaocanOrderEndChargeMsg;
import com.ruoyi.common.core.domain.model.WxRefundReqBody;
import com.ruoyi.common.enums.BoatStatus;
import com.ruoyi.common.enums.OrderStatus;
import com.ruoyi.common.enums.PayStatus;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.handler.websocket.WebSocketMessageHandler;
import com.ruoyi.manager.SocketChannelManager;
import com.ruoyi.manager.WebSocketChannelManager;
import com.ruoyi.pay.service.IWxPayV3Service;
import com.ruoyi.system.service.*;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.*;

@Slf4j
public class MySocketChannelInboundHandler2 extends ChannelInboundHandlerAdapter {

    /**
     * 控制船只实时状态信息更新频率的计数值。
     */
    private static long updateBoatRealtimeStatusInfoCount = 0;
    /**
     * 控制船只电池信息检查频率的计数值。
     */
    private static long checkBoatBatteryInfoCount = 0;
    private static long parseFenceAlarmStatusCount = 0;

    private static Timer keepAliveTimer;
    private static TimerTask keepAliveTimerTask;

    /**
     * 上一个报警信息的id。
     */
    private Long previousAlarmInfoId;

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm");

    private IUserBoatOrderInfoService userBoatOrderInfoService;
    private IBoatAlarmInfoService boatAlarmInfoService;

    public MySocketChannelInboundHandler2() {
        userBoatOrderInfoService = SpringUtils.getBean(IUserBoatOrderInfoService.class);
        boatAlarmInfoService = SpringUtils.getBean(IBoatAlarmInfoService.class);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        //log.info("socket客户端{}已连接.", ctx.channel().remoteAddress().toString());
        SocketChannelManager.addChannel(channel);
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("channelInactive execute.");
        Channel channel = ctx.channel();

        SocketChannelManager.handleSocketChannelInactive(channel);

        super.channelInactive(ctx);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        Channel channel = ctx.channel();
        SysBoat sysBoat = SocketChannelManager.getSysBoatBySocketChannel(channel);
        if (cause instanceof IOException) {
            //IOException异常通常是断开连接触发的异常(比如客户端主动断开或客户端网络异常导致客户端方断开连接等导致)，触发IOException异常后netty会
            // 主动调用channelActive方法，因为此时连接确实已失活，但如果是其他类型异常则并不会调用channelActive方法。
            if (Objects.nonNull(sysBoat)) {
                log.error("exceptionCaught execute,船只信息:[boatCode: {}, boatIdentityId: {} ], channel: {}, cause: {}",
                        sysBoat.getBoatCode(), sysBoat.getBoatIdentityId(), channel, cause.getMessage(), cause);
            } else {
                log.error("exceptionCaught execute,channel: {}, cause: {}", channel, cause.getMessage(), cause);
            }
        } else {
            if (Objects.nonNull(sysBoat)) {
                log.error("exceptionCaught execute,船只信息:[boatCode: {}, boatIdentityId: {} ], channel: {}, cause: {}",
                        sysBoat.getBoatCode(), sysBoat.getBoatIdentityId(), channel, cause.getMessage(), cause);
            } else {
                log.error("exceptionCaught execute,channel: {}, cause: {}", channel, cause.getMessage(), cause);
            }
        }

        //当socket客户端主动关闭连接时会调用exceptionCaught方法，且所抛异常cause的message是“远程主机强迫关闭了一个现有的连接”。另外，
        // 同时还会调用channelInactive方法。
//        log.error("exceptionCaught execute,channel: {}, cause: {}", channel, cause.getMessage(), cause);
//        cause.printStackTrace();

        //主动关闭channel。
//        if (channel.isActive()) channel.close();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//        log.debug("MySocketChannelInboundHandler1 channelRead execute!");
//        log.info("channelRead msg: {}", msg);
        Channel channel = ctx.channel();
        if (msg instanceof BoatMessage) {
            BoatMessage boatMessage = (BoatMessage) msg;
            byte messageType = boatMessage.getMessageType();

            switch (messageType) {
                case MessageType.CONFIG_MESSAGE: //1：配置报文
                    handleConfigMessage(channel, boatMessage);
                    break;
                case MessageType.STATUS_MESSAGE: //3：状态报文
                    handleStatusMessage(channel, boatMessage);
                    break;
                case MessageType.LOG_MESSAGE: //5：日志报文
                    handleLogMessage(channel, boatMessage);
                    break;
                default:
                    log.error("当前报文类型暂不支持解析, messageType: {}, channel: {}", messageType, channel);
            }
        }
    }

    /**
     * 处理配置报文。
     *
     * @param channel
     * @param boatMessage
     */
    private void handleConfigMessage(Channel channel, BoatMessage boatMessage) {
        for (BaseDataPiece dataPiece : boatMessage.getDataPieceList()) {
            int deviceType = dataPiece.getDeviceType();
            //注意：船只注册信息与船只基础配置信息不会在同一个报文中一起发送，而是先发送注册信息配置报文再发基础配置信息报文
            if (deviceType == DeviceTypeContant.BoatRegisterInfo) { //船只注册信息数据段
                BoatRegisterInfo boatRegisterInfo = (BoatRegisterInfo) dataPiece;
                String boatIdentityId = boatRegisterInfo.getBoatIdentityId();

                //检查数据库中是否已存在出厂ID为boatIdentityId的这条船的数据信息
                ISysBoatService sysBoatService = SpringUtils.getBean(ISysBoatService.class);
                SysBoat existSysBoat = sysBoatService.selectSysBoatByBoatIdentityId(boatIdentityId);
                if (Objects.isNull(existSysBoat)) { //后端数据库中不存在boatIdentityId对应的船，说明该船为新船
                    ISysConfigService sysConfigService = SpringUtils.getBean(ISysConfigService.class);
                    String boatDefaultPic = sysConfigService.selectConfigValueByKey("boat_default_pic");

                    SysBoat sysBoat = new SysBoat();
                    sysBoat.setBoatIdentityId(boatIdentityId);
                    if (Objects.nonNull(boatDefaultPic)) {
                        sysBoat.setBoatPic(boatDefaultPic);
                    }

                    sysBoatService.insertSysBoat(sysBoat);
                }

                //发送注册报文的应答报文
                List<BaseDataPiece> dataPieceList = new ArrayList<>();
                //设置读操作 0：无操作，1：配置读，3：无效值
                boatRegisterInfo.setReadFlag((byte) 1);
                //设置报文应答 0：无操作，1：报文应答，2：报文未应答，3：无效值
                boatRegisterInfo.setResponseFlag((byte) 1);
                dataPieceList.add(boatRegisterInfo);
                BoatMessage responseBoatMessage = new BoatMessage();
                BeanUtils.copyProperties(boatMessage, responseBoatMessage);
                responseBoatMessage.setDataPieceList(dataPieceList);
                SocketMessageHandler.sendSocketMsg(channel, responseBoatMessage);
            } else if (deviceType == DeviceTypeContant.BoatBasicConfigInfo) { //船只基础配置信息数据段
                BoatBasicConfigInfo boatBasicConfigInfo = (BoatBasicConfigInfo) dataPiece;
                String boatIdentityId = boatBasicConfigInfo.getBoatIdentityId();

                ISysBoatService sysBoatService = SpringUtils.getBean(ISysBoatService.class);
                SysBoat sysBoat = sysBoatService.selectSysBoatByBoatIdentityId(boatIdentityId);
                if (Objects.nonNull(sysBoat)) { //收到此报文时sysBoat一般不会为null
                    //更新船只的数据
                    sysBoat.setBoatCode(boatBasicConfigInfo.getBoatCode());
                    sysBoat.setProdDate(boatBasicConfigInfo.getProduceDate());
                    sysBoat.setMotorCode1(boatBasicConfigInfo.getMotorCode1());
//            sysBoat.setMotorCode2(motorCode2);
                    sysBoat.setMotorCode2("SP02");
                    sysBoat.setBatteryCoe(boatBasicConfigInfo.getBatteryCode());
                    sysBoat.setRecorderCode(boatBasicConfigInfo.getRecorderCode());
                    //判断当前channel是否已存在
                    Channel existSocketChannel = SocketChannelManager.getSocketChannelByBoatIdentityId(sysBoat.getBoatIdentityId());
                    if (Objects.nonNull(existSocketChannel)) { //同出厂ID的船只对应的连接已存在
                        if (channel.equals(existSocketChannel)) {//已存在的连接与当前连接是同一个，说明当前配置报文是船端在与后端已保持连接的情况下修改船端配置后发送的
                            //更新船只信息对象
                            SocketChannelManager.updateOnlineBoatInfo(sysBoat);
                        } else { //已存在的连接与当前新连接不是同一个，此时说明同一个船只又重新创建了一个连接，前一个连接已失效。
                            //此时需将前一个已失效的连接断开(如果当前其仍处于连接状态)，并清除掉。
                            if (existSocketChannel.isActive()) {
                                //主动关闭channel后会自动调用MySocketChannelInboundHandler中的channelInactive方法，所以此处无需手动调用。
                                existSocketChannel.close();
                            }

                            //处理新的船只连接。
                            processNewBoatChannel(channel, boatIdentityId, sysBoat);
                        }
                    } else { //未找到同出厂ID的船只对应的连接，说明当前是一个新连接发送的配置信息报文
                        //处理新的船只连接。
                        processNewBoatChannel(channel, boatIdentityId, sysBoat);
                    }

                    //更新后端数据库中当前船只的配置信息(不管是新建立的连接还是连接中收到的配置报文都需更新数据库中船只信息)
                    sysBoatService.updateSysBoat(sysBoat);
                }

                //发送船只基本配置信息报文的响应报文
                List<BaseDataPiece> dataPieceList = new ArrayList<>();
                //设置读操作 0：无操作，1：配置读，3：无效值
                boatBasicConfigInfo.setReadFlag((byte) 1);
                //设置报文应答 0：无操作，1：报文应答，2：报文未应答，3：无效值
                boatBasicConfigInfo.setResponseFlag((byte) 1); //设置报文应答
                dataPieceList.add(boatBasicConfigInfo);
                BoatMessage responseBoatMessage = new BoatMessage();
                BeanUtils.copyProperties(boatMessage, responseBoatMessage);
                responseBoatMessage.setDataPieceList(dataPieceList);
                SocketMessageHandler.sendSocketMsg(channel, responseBoatMessage);
            }
        }
    }

    /**
     * 处理新的船只连接。
     *
     * @param channel
     * @param boatIdentityId
     * @param sysBoat
     */
    private static void processNewBoatChannel(Channel channel, String boatIdentityId, SysBoat sysBoat) {

        SocketChannelManager.addOnlineBoatChannel(channel, sysBoat);

        Long scenicId = sysBoat.getScenicId();
        Long boatId = sysBoat.getBoatId();
        //注意：此处只能在之前船只的状态的"离线"或"维护中"时才能更新其状态为“空闲”，其他非“离线”或非"维护中"状态不能更新，保持默认即可
        if (sysBoat.getStatus() == BoatStatus.Offline.getCode() || sysBoat.getStatus() == BoatStatus.Maintaining.getCode()) {
            //需判断船只所属的景区id是否为空，因为如果没有在后台为船只分配所属景区的话则此处船只所属的景区id就会为空
            if (Objects.nonNull(scenicId)) {
                Channel scenicWebSocketChannel = WebSocketChannelManager.getScenicWebSocketChannelByScenicId(scenicId);
                //此处需判断scenicWebSocketChannel是否为空，因为当前船只所属的景区的管理员此时可能未登录监控后台，未登录的话scenicWebSocketChannel就会为空
                if (Objects.nonNull(scenicWebSocketChannel)) {
                    //向景区监控后台发送船只状态更新的消息
                    BoatOperationStatus boatOperationStatus = new BoatOperationStatus();
                    boatOperationStatus.setBoatId(boatId);
                    boatOperationStatus.setBoatIdentityId(boatIdentityId);
                    boatOperationStatus.setStatus(BoatStatus.Idle.getCode());
                    boatOperationStatus.setBoatCode(sysBoat.getBoatCode());

                    WebSocketMessageHandler.sendMsgToScenicAdmin(scenicId, boatOperationStatus);
                }
            }

            //更新船只的状态为“空闲”，刚建立连接的船端状态默认为“空闲”
            sysBoat.setStatus(BoatStatus.Idle.getCode());
            sysBoat.setStatusDesc(BoatStatus.Idle.getInfo());
        }

        //船只建立新连接后服务端主动给其发送一次码头数据，但需确保当前船只已分配了码头
        Long wharfId = sysBoat.getWharfId();
        if (Objects.nonNull(wharfId) && wharfId != 0) {
            ISysWharfService sysWharfService = SpringUtils.getBean(ISysWharfService.class);
            SysWharf sysWharf = sysWharfService.selectWharfByWharfId(wharfId);
            if (Objects.nonNull(sysWharf)) {
                SocketMessageHandler.sendWharfInfoToAssignedOnlineBoat(boatId, sysWharf);
            } else {
                SocketMessageHandler.sendWharfInfoToAssignedOnlineBoat(boatId, null);
            }
        } else {
            SocketMessageHandler.sendWharfInfoToAssignedOnlineBoat(boatId, null);
        }

        //船只建立新连接后服务端主动给其发送一次自动航线数据，但需确保当前船只已分配了自动航线
        Long[] shiplineIds = sysBoat.getShiplineIds();
        if (Objects.nonNull(shiplineIds) && shiplineIds.length > 0) {
            //当前默认取第一个自动航线
            Long firstShiplineId = shiplineIds[0];
            ISysShiplineService sysShiplineService = SpringUtils.getBean(ISysShiplineService.class);
            SysShipline sysShipline = sysShiplineService.selectSysShiplineByShiplineId(firstShiplineId);
            if (Objects.nonNull(sysShipline)) {
                SocketMessageHandler.sendShiplineInfoToAssignedOnlineBoat(boatId, sysShipline);
            } else {
                SocketMessageHandler.sendShiplineInfoToAssignedOnlineBoat(boatId, null);
            }
        } else {
            SocketMessageHandler.sendShiplineInfoToAssignedOnlineBoat(boatId, null);
        }

        //船只建立新连接后服务端主动给其发送一次电子围栏数据，但需确保当前船只已分配了电子围栏
        Long fenceId = sysBoat.getFenceId();
        if (Objects.nonNull(fenceId) && fenceId != 0) {
            ISysFenceService sysFenceService = SpringUtils.getBean(ISysFenceService.class);
            SysFence sysFence = sysFenceService.selectFenceById(fenceId);
            if (Objects.nonNull(sysFence)) {
                SocketMessageHandler.sendFenceInfoToAssignedOnlineBoat(boatId, sysFence);
            } else {
                SocketMessageHandler.sendFenceInfoToAssignedOnlineBoat(boatId, null);
            }
        } else {
            SocketMessageHandler.sendFenceInfoToAssignedOnlineBoat(boatId, null);
        }

        //查询当前船只是否存在未完成的订单
    }

    /**
     * 处理状态报文。
     *
     * @param channel
     * @param boatMessage
     */
    private void handleStatusMessage(Channel channel, BoatMessage boatMessage) {
        PaopaoTunBoatInfo paopaoTunBoatInfo = null;
        NavigationInfo navigationInfo = null;
        for (BaseDataPiece dataPiece : boatMessage.getDataPieceList()) {
            int deviceType = dataPiece.getDeviceType();
            if (deviceType == DeviceTypeContant.PaopaoTunInfo) {
                paopaoTunBoatInfo = (PaopaoTunBoatInfo) dataPiece;
            } else if (deviceType == DeviceTypeContant.NavigationInfo) {
                navigationInfo = (NavigationInfo) dataPiece;
            }
        }

        BoatRealtimeStatusInfo boatRealtimeStatusInfo = new BoatRealtimeStatusInfo();
        if (Objects.nonNull(paopaoTunBoatInfo)) {
            //boatRealtimeStatusInfo.setBoatStatus(translatePaopaotunBoatStatusToRealtimeBoatStatus(paopaoTunBoatInfo.getBoatStatus()));
            boatRealtimeStatusInfo.setBoatStatus(paopaoTunBoatInfo.getBoatStatus());
            boatRealtimeStatusInfo.setPayStatus(paopaoTunBoatInfo.getPayStatus());
            boatRealtimeStatusInfo.setSafetyInstructionConfirStatus(paopaoTunBoatInfo.getSafetyInstructionConfirStatus());
            boatRealtimeStatusInfo.setUnlockStatus(paopaoTunBoatInfo.getUnlockStatus());
            boatRealtimeStatusInfo.setLockStatus(paopaoTunBoatInfo.getLockStatus());
            boatRealtimeStatusInfo.setAutoEntryPortStatus(paopaoTunBoatInfo.getAutoEntryPortStatus());
            boatRealtimeStatusInfo.setAutoShiplineStatus(paopaoTunBoatInfo.getAutoShiplineStatus());
            boatRealtimeStatusInfo.setTaskDataUpdateTime(paopaoTunBoatInfo.getTaskDataUpdateTime());
            boatRealtimeStatusInfo.setFenceUpdateTime(paopaoTunBoatInfo.getFenceUpdateTime());
            boatRealtimeStatusInfo.setOnceDuration(paopaoTunBoatInfo.getOnceDuration());
            boatRealtimeStatusInfo.setAggregateDuration(paopaoTunBoatInfo.getAggregateDuration());
            boatRealtimeStatusInfo.setOnceDistance(paopaoTunBoatInfo.getOnceDistance());
            boatRealtimeStatusInfo.setAggregateDistance(paopaoTunBoatInfo.getAggregateDistance());
            boatRealtimeStatusInfo.setOncePowerConsumption(paopaoTunBoatInfo.getOncePowerConsumption());
            boatRealtimeStatusInfo.setAggregatePowerConsumption(paopaoTunBoatInfo.getAggregatePowerConsumption());
            boatRealtimeStatusInfo.setRemainTime(paopaoTunBoatInfo.getRemainTime());
            boatRealtimeStatusInfo.setRemainDistance(paopaoTunBoatInfo.getRemainDistance());
            boatRealtimeStatusInfo.setBackTime(paopaoTunBoatInfo.getBackTime());
            boatRealtimeStatusInfo.setBackDistance(paopaoTunBoatInfo.getBackDistance());
            boatRealtimeStatusInfo.setWharfConfigDataUpdateTime(paopaoTunBoatInfo.getWharfConfigDataUpdateTime());
            boatRealtimeStatusInfo.setFenceAlarmStatus(paopaoTunBoatInfo.getFenceAlarmStatus());
        }

        if (Objects.nonNull(navigationInfo)) {
            boatRealtimeStatusInfo.setCurrTime(navigationInfo.getCurrTime());
            boatRealtimeStatusInfo.setSatelliteNum(navigationInfo.getSatelliteNum());
            //将船速从节/h换算成km/h
            boatRealtimeStatusInfo.setSpeed(Float.valueOf(new DecimalFormat("0.0").format(navigationInfo.getSog() * 0.01 * 1.852))); //1kn=1节=1海里/小时=1.852公里/小时=0.514444m/s
            //boatRealtimeStatusInfo.setOrientation(Float.valueOf(new DecimalFormat("0.00").format(navigationInfo.getHdg() * 0.01)));
            boatRealtimeStatusInfo.setOrientation(navigationInfo.getHdg());
            boatRealtimeStatusInfo.setLon(navigationInfo.getLon());
            boatRealtimeStatusInfo.setLat(navigationInfo.getLat());
        }

        //处理船只实时状态信息
        handleBoatRealtimeStatusInfo(channel, boatRealtimeStatusInfo);
    }

    /**
     * 处理船只实时状态信息。
     *
     * @param channel
     * @param boatRealtimeStatusInfo
     */
    private void handleBoatRealtimeStatusInfo(Channel channel, BoatRealtimeStatusInfo boatRealtimeStatusInfo) {
        //获取当前连接通道绑定的相关信息
        SocketChannelManager.ChannelInfo channelInfo = SocketChannelManager.getChannelInfoBySocketChannel(channel);
        if (Objects.isNull(channelInfo)) {
            log.info("channelInfo is null");
            return;
        }

        //获取当前连接通道对应的船只信息
        SysBoat sysBoat = SocketChannelManager.getSysBoatBySocketChannel(channel);
        if (Objects.isNull(sysBoat)) {
            log.info("sysBoat is null");
            return;
        }
        Long scenicId = sysBoat.getScenicId();

        //解析安全须知确认状态(目的是捕捉开锁与解锁状态变化)
        parseSafetyInstructionConfirStatus(channel, channelInfo, boatRealtimeStatusInfo.getSafetyInstructionConfirStatus());

        //解析电子围栏报警状态
        parseFenceAlarmStatus(sysBoat, channelInfo, boatRealtimeStatusInfo.getFenceAlarmStatus());
        channelInfo.setFenceAlarmStatus(boatRealtimeStatusInfo.getFenceAlarmStatus());

        //解析船只的状态
        parseBoatStatus(sysBoat, channelInfo, boatRealtimeStatusInfo.getBoatStatus());
        log.debug("接收到船只[boatCode={},boatIdentityId={}]的状态信息boatRealtimeStatusInfo：{}", sysBoat.getBoatCode(), sysBoat.getBoatIdentityId(), boatRealtimeStatusInfo);

        //给景区监控中心发送船只实时状态信息
        boatRealtimeStatusInfo.setSysBoat(sysBoat);
        WebSocketMessageHandler.sendMsgToScenicAdmin(scenicId, boatRealtimeStatusInfo);

        SocketChannelManager.updateChannelInfo(channel, channelInfo);

        if (updateBoatRealtimeStatusInfoCount >= Long.MAX_VALUE) {
            updateBoatRealtimeStatusInfoCount = 0;
        }

        //状态报文每0.5秒收到一次，如下为每间隔10次(即5秒)更新一下数据库中船只的位置信息数据
        if (updateBoatRealtimeStatusInfoCount % 10 == 0) {
            log.debug("===更新船只部分状态信息(不含实时运营状态)===");
            Long boatId = sysBoat.getBoatId();
            SysBoat newSysBoat = new SysBoat();
            newSysBoat.setBoatId(boatId);
            newSysBoat.setLastLongitude(boatRealtimeStatusInfo.getLon());
            newSysBoat.setLastLatitude(boatRealtimeStatusInfo.getLat());
            newSysBoat.setLastOrientation(boatRealtimeStatusInfo.getOrientation());
            newSysBoat.setAccumulatedTime(boatRealtimeStatusInfo.getAggregateDuration());
            newSysBoat.setAccumulatedDistance(boatRealtimeStatusInfo.getAggregateDistance());
//                        newSysBoat.setAccumulatedTime(boatRealtimeStatusInfo.getAggregateDuration());
            ISysBoatService sysBoatService = SpringUtils.getBean(ISysBoatService.class);
//                        sysBoatService.updateBoatLocationInfo(sysBoat.getBoatId(), boatRealtimeStatusInfo.getLon(), boatRealtimeStatusInfo.getLat(), boatRealtimeStatusInfo.getOrientation());
            sysBoatService.updateSysBoat(newSysBoat);
        }

        updateBoatRealtimeStatusInfoCount++;
    }

    /**
     * 处理日志报文。
     *
     * @param channel
     * @param boatMessage
     */
    private void handleLogMessage(Channel channel, BoatMessage boatMessage) {
        BatteryInfo batteryInfo = null;
        BoatErrorCodeInfo boatErrorCodeInfo = null;
        //所有的日志类型数据段都是放在一个日志报文中一起发送的
        for (BaseDataPiece dataPiece : boatMessage.getDataPieceList()) {
            int deviceType = dataPiece.getDeviceType();
            if (deviceType == DeviceTypeContant.BatteryInfo) {
                batteryInfo = (BatteryInfo) dataPiece;
            } else if (deviceType == DeviceTypeContant.BoatErrorCodeInfo) {
                boatErrorCodeInfo = (BoatErrorCodeInfo) dataPiece;
            }
        }

        if (Objects.nonNull(batteryInfo)) {
            handleBatteryInfo(channel, batteryInfo);
        }

        if (Objects.nonNull(boatErrorCodeInfo)) {
            handleBoatErrorCodeInfo(channel, boatErrorCodeInfo);
        }
    }

    private void handleBoatErrorCodeInfo(Channel channel, BoatErrorCodeInfo boatErrorCodeInfo) {
        //获取当前连接通道绑定的相关信息
        SocketChannelManager.ChannelInfo channelInfo = SocketChannelManager.getChannelInfoBySocketChannel(channel);
        if (Objects.isNull(channelInfo)) {
            log.info("channelInfo is null");
            return;
        }

        //获取当前连接通道对应的船只信息
        SysBoat sysBoat = SocketChannelManager.getSysBoatBySocketChannel(channel);
        if (Objects.isNull(sysBoat)) {
            log.info("sysBoat is null");
            return;
        }
        UserBoatOrderInfo userBoatOrderInfo = userBoatOrderInfoService.getUserBoatOrderInfoOfUsingStatusByBoatIdentityId(sysBoat.getBoatIdentityId());

        List<BoatErrorCodeInfo.BoatErrorCode> currBoatErrorCodeList = boatErrorCodeInfo.getBoatErrorCodeList();
        List<BoatErrorCodeInfo.BoatErrorCode> preBoatErrorCodeList = channelInfo.getBoatErrorCodeList();

        for (int i = 0; i < 10; i++) {
            BoatErrorCodeInfo.BoatErrorCode preBoatErrorCode = preBoatErrorCodeList.get(i);
            int preSpn = preBoatErrorCode.getSpn();
            byte preFmi = preBoatErrorCode.getFmi();
            byte preCm = preBoatErrorCode.getCm();
            byte preOc = preBoatErrorCode.getOc();

            BoatErrorCodeInfo.BoatErrorCode currBoatErrorCode = currBoatErrorCodeList.get(i);
            int currSpn = currBoatErrorCode.getSpn();
            byte currFmi = currBoatErrorCode.getFmi();
            byte currCm = currBoatErrorCode.getCm();
            byte currOc = currBoatErrorCode.getOc();

            Object[] boatInfoArr = {sysBoat.getBoatCode(), sysBoat.getBoatIdentityId(), sysBoat.getScenicId()};
            String logFormatPrefix = "船只[boatCode:{},boatIdentityId:{},scenicId:{}]";

            if (i == 0) { //方向机掉线
                if (preSpn == 200 && preFmi == 0 && preCm == 1) { //之前存在"方向机掉线"的报警
                    if (currSpn == 200 && currFmi == 0 && currCm == 1) { //当前也发生"方向机掉线"的报警
                        //无需处理
                    } else { //当前未发生"方向机掉线"的报警
                        //此时"方向机掉线"的报警消除
                        log.info(logFormatPrefix + "方向机掉线报警消除!", boatInfoArr);
                    }
                } else { //之前不存在"方向机掉线"的报警
                    if (currSpn == 200 && currFmi == 0 && currCm == 1) { //当前发生"方向机掉线"的报警
                        //此时"方向机掉线"的报警触发，需处理
                        log.info(logFormatPrefix + "触发方向机掉线报警！", boatInfoArr);

                        handleBoatErrorCodeAlarm(0, sysBoat, userBoatOrderInfo);
                    } else { //当前未发生"方向机掉线"的报警
                        //无需处理
                    }
                }
            } else if (i == 1) { //油门手柄掉线
                if (preSpn == 200 && preFmi == 1 && preCm == 1) { //之前存在"油门手柄掉线"的报警
                    if (currSpn == 200 && currFmi == 1 && currCm == 1) { //当前也发生"油门手柄掉线"的报警
                        //无需处理
                    } else { //当前未发生"油门手柄掉线"的报警
                        log.info(logFormatPrefix + "油门手柄掉线报警消除!", boatInfoArr);
                    }
                } else { //之前不存在"方向机掉线"的报警
                    if (currSpn == 200 && currFmi == 1 && currCm == 1) { //当前发生"方向机掉线"的报警
                        log.info(logFormatPrefix + "触发油门手柄掉线报警!", boatInfoArr);

                        handleBoatErrorCodeAlarm(1, sysBoat, userBoatOrderInfo);
                    } else { //当前未发生"方向机掉线"的报警
                        //无需处理
                    }
                }
            } else if (i == 2) { //惯导掉线
                if (preSpn == 200 && preFmi == 2 && preCm == 1) { //之前存在"惯导掉线"的报警
                    if (currSpn == 200 && currFmi == 2 && currCm == 1) {
                        //无需处理
                    } else {
                        log.info(logFormatPrefix + "惯导掉线报警消除!", boatInfoArr);
                    }
                } else {
                    if (currSpn == 200 && currFmi == 2 && currCm == 1) { //当前发生"方向机掉线"的报警
                        log.info(logFormatPrefix + "触发惯导掉线报警!", boatInfoArr);

                        handleBoatErrorCodeAlarm(2, sysBoat, userBoatOrderInfo);
                    } else {
                        //无需处理
                    }
                }
            } else if (i == 3) { //毫米波掉线
                if (preSpn == 200 && preFmi == 3 && preCm == 1) { //之前存在"毫米波掉线"的报警
                    if (currSpn == 200 && currFmi == 3 && currCm == 1) {
                        //无需处理
                    } else {
                        log.info(logFormatPrefix + "毫米波掉线报警消除!", boatInfoArr);
                    }
                } else {
                    if (currSpn == 200 && currFmi == 3 && currCm == 1) { //当前发生"毫米波掉线"的报警
                        log.info(logFormatPrefix + "触发毫米波掉线报警!", boatInfoArr);

                        handleBoatErrorCodeAlarm(3, sysBoat, userBoatOrderInfo);
                    } else {
                        //无需处理
                    }
                }
            } else if (i == 4) { //电机0掉线
                if (preSpn == 200 && preFmi == 4 && preCm == 1) {
                    if (currSpn == 200 && currFmi == 4 && currCm == 1) {
                        //无需处理
                    } else {
                        log.info(logFormatPrefix + "电机0掉线报警消除!", boatInfoArr);
                    }
                } else {
                    if (currSpn == 200 && currFmi == 4 && currCm == 1) {
                        log.info(logFormatPrefix + "触发电机0掉线报警!", boatInfoArr);

                        handleBoatErrorCodeAlarm(4, sysBoat, userBoatOrderInfo);
                    } else {
                        //无需处理
                    }
                }
            } else if (i == 5) { //电机1掉线
                if (preSpn == 200 && preFmi == 5 && preCm == 1) {
                    if (currSpn == 200 && currFmi == 5 && currCm == 1) {
                        //无需处理
                    } else {
                        log.info(logFormatPrefix + "电机1掉线报警消除!", boatInfoArr);
                    }
                } else {
                    if (currSpn == 200 && currFmi == 5 && currCm == 1) {
                        log.info(logFormatPrefix + "触发电机1掉线报警!", boatInfoArr);

                        handleBoatErrorCodeAlarm(5, sysBoat, userBoatOrderInfo);
                    } else {
                        //无需处理
                    }
                }
            } else if (i == 6) { //电池掉线
                if (preSpn == 200 && preFmi == 6 && preCm == 1) {
                    if (currSpn == 200 && currFmi == 6 && currCm == 1) {
                        //无需处理
                    } else {
                        log.info(logFormatPrefix + "电池掉线报警消除!", boatInfoArr);
                    }
                } else {
                    if (currSpn == 200 && currFmi == 6 && currCm == 1) {
                        log.info(logFormatPrefix + "触发电池掉线报警!", boatInfoArr);

                        handleBoatErrorCodeAlarm(6, sysBoat, userBoatOrderInfo);
                    } else {
                        //无需处理
                    }
                }
            } else if (i == 7) { //机械执行器0掉线
                if (preSpn == 200 && preFmi == 7 && preCm == 1) {
                    if (currSpn == 200 && currFmi == 7 && currCm == 1) {
                        //无需处理
                    } else {
                        log.info(logFormatPrefix + "机械执行器0掉线报警消除!", boatInfoArr);
                    }
                } else {
                    if (currSpn == 200 && currFmi == 7 && currCm == 1) {
                        log.info(logFormatPrefix + "触发机械执行器0掉线报警!", boatInfoArr);

                        handleBoatErrorCodeAlarm(7, sysBoat, userBoatOrderInfo);
                    } else {
                        //无需处理
                    }
                }
            } else if (i == 8) { //机械执行器1掉线
                if (preSpn == 200 && preFmi == 8 && preCm == 1) {
                    if (currSpn == 200 && currFmi == 8 && currCm == 1) {
                        //无需处理
                    } else {
                        log.info(logFormatPrefix + "机械执行器1掉线报警消除!", boatInfoArr);
                    }
                } else {
                    if (currSpn == 200 && currFmi == 8 && currCm == 1) {
                        log.info(logFormatPrefix + "触发机械执行器1掉线报警!", boatInfoArr);

                        handleBoatErrorCodeAlarm(8, sysBoat, userBoatOrderInfo);
                    } else {
                        //无需处理
                    }
                }
            }
        }

        channelInfo.setBoatErrorCodeList(currBoatErrorCodeList);
    }

    /**
     * 处理故障码报警。
     *
     * @param type              故障码类型。0：方向机离线；1：油门手柄掉线；2：惯导掉线；3：毫米波掉线，4：电机0掉线，5：电机1掉线，6：电池掉线，7：机械执行器0掉线，8：机械执行器1掉线
     * @param sysBoat
     * @param userBoatOrderInfo
     */
    private void handleBoatErrorCodeAlarm(int type, SysBoat sysBoat, UserBoatOrderInfo userBoatOrderInfo) {
        //创建BoatAlarmInfo对象
        BoatAlarmInfo boatAlarmInfo = new BoatAlarmInfo();
        if (type == 0) {
            boatAlarmInfo.setType(AlarmType.SteeringGear_Off);
            boatAlarmInfo.setContent("方向机掉线");
        } else if (type == 1) {
            boatAlarmInfo.setType(AlarmType.AcceleratorHandle_Off);
            boatAlarmInfo.setContent("油门手柄掉线");
        } else if (type == 2) {
            boatAlarmInfo.setType(AlarmType.Navigation_Off);
            boatAlarmInfo.setContent("惯导掉线");
        } else if (type == 3) {
            boatAlarmInfo.setType(AlarmType.MillimeterWave_Off);
            boatAlarmInfo.setContent("毫米波掉线");
        } else if (type == 4) {
            boatAlarmInfo.setType(AlarmType.Motor0_Off);
            boatAlarmInfo.setContent("电机0掉线");
        } else if (type == 5) {
            boatAlarmInfo.setType(AlarmType.Motor1_Off);
            boatAlarmInfo.setContent("电机1掉线");
        } else if (type == 6) {
            boatAlarmInfo.setType(AlarmType.Battery_Off);
            boatAlarmInfo.setContent("电池掉线");
        } else if (type == 7) {
            boatAlarmInfo.setType(AlarmType.MechanicalActuator0_Off);
            boatAlarmInfo.setContent("机械执行器0掉线");
        } else if (type == 8) {
            boatAlarmInfo.setType(AlarmType.MechanicalActuator1_Off);
            boatAlarmInfo.setContent("机械执行器1掉线");
        } else {
            log.warn("故障码类型[type={}]不符合要求，无法处理当前的故障报警！", type);
            return;
        }

        if (Objects.nonNull(userBoatOrderInfo)) {
            boatAlarmInfo.setPhone(userBoatOrderInfo.getUserPhone());
        }
        boatAlarmInfo.setLevel(AlarmLevel.Urgent_Type);  //设置报警级别为“紧急”。
        boatAlarmInfo.setStatus(AlarmResolveStatus.Unresolve); //设置报警的处理状态为"未处理"。
        boatAlarmInfo.setBoatIdentityId(sysBoat.getBoatIdentityId());
        boatAlarmInfo.setBoatId(sysBoat.getBoatId());
        boatAlarmInfo.setBoatCode(sysBoat.getBoatCode());
        boatAlarmInfo.setScenicId(sysBoat.getScenicId());

        //给景区监控中心发送船只报警状态信息
        WebSocketMessageHandler.sendMsgToScenicAdmin(sysBoat.getScenicId(), boatAlarmInfo);

        //将报警信息插入到数据库中保存
        boatAlarmInfoService.insertBoatAlarmInfo(boatAlarmInfo);
    }

    private void handleBatteryInfo(Channel channel, BatteryInfo batteryInfo) {
        //获取当前连接通道绑定的相关信息
        SocketChannelManager.ChannelInfo channelInfo = SocketChannelManager.getChannelInfoBySocketChannel(channel);
        if (Objects.isNull(channelInfo)) {
            log.info("channelInfo is null");
            return;
        }

        //获取当前连接通道对应的船只信息
        SysBoat sysBoat = SocketChannelManager.getSysBoatBySocketChannel(channel);
        if (Objects.isNull(sysBoat)) {
            log.info("sysBoat is null");
            return;
        }

        //给景区监控中心发送船只实时状态信息
        batteryInfo.setSysBoat(sysBoat);
        Long scenicId = sysBoat.getScenicId();
        WebSocketMessageHandler.sendMsgToScenicAdmin(scenicId, batteryInfo);

        if (checkBoatBatteryInfoCount >= Long.MAX_VALUE) {
            checkBoatBatteryInfoCount = 0;
        }

        //日志报文每0.5秒收到一次，如下为每间隔10次(即5秒)检查一下船只的电池状态信息数据
        if (checkBoatBatteryInfoCount % 10 == 0) {
            //获取上一次电池的电量。
            byte previousBatteryVolume = channelInfo.getBatteryVolume();
            //获取上一次电池的温度。
            int previousBatteryTemp = channelInfo.getBatteryTemperature();
            log.info("previousBatteryVolume: {}, previousBatteryTemp: {}", previousBatteryVolume, previousBatteryTemp);

            //获取当前电池电量百分比值
            byte currBatteryVolume = batteryInfo.getSoc();
            int currMaximumTemp = batteryInfo.getMaximumTemperature();
            log.info("currBatteryVolume: {}, currMaximumTemp: {}", currBatteryVolume, currMaximumTemp);

            ISysConfigService sysConfigService = SpringUtils.getBean(ISysConfigService.class);
            //从redis缓存或数据库中读取船只电池电量报警值
            String batteryVolumeAlarmValueStr = sysConfigService.selectConfigValueByKey("batteryVolumeAlarmValue");
            int batteryVolumeAlarmValue;
            if (StringUtils.isNotEmpty(batteryVolumeAlarmValueStr)) {
                batteryVolumeAlarmValue = Integer.valueOf(batteryVolumeAlarmValueStr);
            } else {
                batteryVolumeAlarmValue = 25;
            }

            //从redis缓存或数据库中读取船只电池温度报警值
            String batteryTempAlarmValueStr = sysConfigService.selectConfigValueByKey("batteryTempAlarmValue");
            int batteryTempAlarmValue;
            if (StringUtils.isNotEmpty(batteryTempAlarmValueStr)) {
                batteryTempAlarmValue = Integer.valueOf(batteryTempAlarmValueStr);
            } else {
                batteryTempAlarmValue = 50;
            }

            log.info("batteryVolumeAlarmValue: {}, batteryTempAlarmValue: {}", batteryVolumeAlarmValue, batteryTempAlarmValue);

            //判断当前船只的电池电量是否低于报警值
            if (previousBatteryVolume >= batteryVolumeAlarmValue && currBatteryVolume < batteryVolumeAlarmValue) {
                //再判断此低电量报警是否已发出过，需确保每次触发后只发送一次报警提醒
                if (!channelInfo.isBatteryVolumeAlarmSend()) {
                    log.warn("首次检测到电池电量降到报警值{}%以下！", batteryVolumeAlarmValue);
                    channelInfo.setBatteryVolumeAlarmSend(true);

                    //创建BoatAlarmInfo对象
                    BoatAlarmInfo boatAlarmInfo = new BoatAlarmInfo();
                    boatAlarmInfo.setBoatIdentityId(sysBoat.getBoatIdentityId());
                    boatAlarmInfo.setBoatId(sysBoat.getBoatId());
                    boatAlarmInfo.setBoatCode(sysBoat.getBoatCode());
                    boatAlarmInfo.setScenicId(sysBoat.getScenicId());
                    boatAlarmInfo.setType(AlarmType.Battery_Volume_Alarm); //设置报警类型为电池电量报警。
                    UserBoatOrderInfo userBoatOrderInfo = userBoatOrderInfoService.getUserBoatOrderInfoOfUsingStatusByBoatIdentityId(sysBoat.getBoatIdentityId());
                    if (Objects.nonNull(userBoatOrderInfo)) {
                        boatAlarmInfo.setPhone(userBoatOrderInfo.getUserPhone());
                    }
                    boatAlarmInfo.setLevel(AlarmLevel.Urgent_Type);  //设置报警级别为“紧急”。
                    boatAlarmInfo.setStatus(AlarmResolveStatus.Unresolve); //设置报警的处理状态为"未处理"。
                    boatAlarmInfo.setContent("电池电量低");

                    IBoatAlarmInfoService boatAlarmInfoService = SpringUtils.getBean(IBoatAlarmInfoService.class);
                    //将报警信息插入到数据库中保存
                    if (Objects.nonNull(boatAlarmInfoService)) {
                        boatAlarmInfoService.insertBoatAlarmInfo(boatAlarmInfo);
                    }

                    //给景区监控中心发送船只报警状态信息
                    WebSocketMessageHandler.sendMsgToScenicAdmin(sysBoat.getScenicId(), boatAlarmInfo);
                }
            } else if (previousBatteryVolume <= batteryVolumeAlarmValue && currBatteryVolume > batteryVolumeAlarmValue) { //电池电量升到报警值之上
                channelInfo.setBatteryVolumeAlarmSend(false);
            }

            //判断当前船只的电池温度是否高于报警值，注意：需要排除高于100度的异常值，电池上传的温度值有可能是128等异常值。
            if (previousBatteryTemp <= batteryTempAlarmValue && currMaximumTemp > batteryTempAlarmValue && currMaximumTemp < 100) {
                //再判断此电池温度高报警是否已发出过，需确保每次触发后只发送一次报警提醒
                if (!channelInfo.isBatteryTemperatureAlarmSend()) {
                    log.warn("首次检测到电池温度升高到报警值{}以上！", batteryTempAlarmValue);
                    channelInfo.setBatteryTemperatureAlarmSend(true);
                    UserBoatOrderInfo userBoatOrderInfo = userBoatOrderInfoService.getUserBoatOrderInfoOfUsingStatusByBoatIdentityId(sysBoat.getBoatIdentityId());
                    //创建BoatAlarmInfo对象
                    BoatAlarmInfo boatAlarmInfo = new BoatAlarmInfo();
                    boatAlarmInfo.setBoatIdentityId(sysBoat.getBoatIdentityId());
                    boatAlarmInfo.setBoatId(sysBoat.getBoatId());
                    boatAlarmInfo.setBoatCode(sysBoat.getBoatCode());
                    boatAlarmInfo.setScenicId(sysBoat.getScenicId());
                    boatAlarmInfo.setType(AlarmType.Battery_Temperature_Alarm); //设置报警类型为电池温度报警。
                    if (Objects.nonNull(userBoatOrderInfo)) {
                        boatAlarmInfo.setPhone(userBoatOrderInfo.getUserPhone());
                    }
                    boatAlarmInfo.setLevel(AlarmLevel.Urgent_Type);  //设置报警级别为“紧急”。
                    boatAlarmInfo.setStatus(AlarmResolveStatus.Unresolve); //设置报警的处理状态为"未处理"。
                    boatAlarmInfo.setContent("电池温度高");

                    IBoatAlarmInfoService boatAlarmInfoService = SpringUtils.getBean(IBoatAlarmInfoService.class);
                    //将报警信息插入到数据库中保存
                    if (Objects.nonNull(boatAlarmInfoService)) {
                        boatAlarmInfoService.insertBoatAlarmInfo(boatAlarmInfo);
                    }

                    //给景区监控中心发送船只报警状态信息
                    WebSocketMessageHandler.sendMsgToScenicAdmin(sysBoat.getScenicId(), boatAlarmInfo);
                }
            } else if (previousBatteryTemp >= batteryTempAlarmValue && currMaximumTemp < batteryTempAlarmValue) { //电池温度降到报警值以下
                channelInfo.setBatteryTemperatureAlarmSend(false);
            }

            channelInfo.setBatteryVolume(currBatteryVolume);
            channelInfo.setBatteryTemperature(currMaximumTemp);
        }

        checkBoatBatteryInfoCount++;
    }

    /**
     * 解析船只的状态。
     *
     * @param sysBoat
     * @param channelInfo
     * @param currBoatStatus 当前船只报文中的状态。可取值：0：无操作，1：空闲，2：手动，3：自动，4：离线，5：维护，6-14：保留，15：无效值
     */
    private void parseBoatStatus(SysBoat sysBoat, SocketChannelManager.ChannelInfo channelInfo, byte currBoatStatus) {
        if (Objects.isNull(sysBoat)) return;

        ISysBoatService sysBoatService;
        //获取上一次的船只状态值
        byte previousBoatStatus = channelInfo.getBoatStatus();
        if (previousBoatStatus != 1 && currBoatStatus == 1) { //船只状态从非空闲变为“空闲”
            //更新数据库中当前船只的状态为"空闲" (备注：此处是否需要更新待慎重考虑，因为可能会影响小程序订单过程中改变的船只状态)
            sysBoatService = SpringUtils.getBean(ISysBoatService.class);
            if (Objects.nonNull(sysBoatService)) {
                sysBoatService.updateBoatStatusByBoadId(sysBoat.getBoatId(), BoatStatus.Idle);
            }
        } else if (previousBoatStatus != 2 && currBoatStatus == 2) { //船只状态变为“手动”
            sysBoatService = SpringUtils.getBean(ISysBoatService.class);
            if (Objects.nonNull(sysBoatService)) {
                sysBoatService.updateBoatStatusByBoadId(sysBoat.getBoatId(), BoatStatus.ManualDriving);
            }
        } else if (previousBoatStatus != 3 && currBoatStatus == 3) { //船只状态变为“自动”
            sysBoatService = SpringUtils.getBean(ISysBoatService.class);
            if (Objects.nonNull(sysBoatService)) {
                sysBoatService.updateBoatStatusByBoadId(sysBoat.getBoatId(), BoatStatus.AutoDriving);
            }
        }

        if (currBoatStatus == 1) { //当前为“空闲”状态
            sysBoat.setStatus(BoatStatus.Idle.getCode());
            sysBoat.setStatusDesc(BoatStatus.Idle.getInfo());
        } else if (currBoatStatus == 2) { //当前为“手动驾驶”状态
            sysBoat.setStatus(BoatStatus.ManualDriving.getCode());
            sysBoat.setStatusDesc(BoatStatus.ManualDriving.getInfo());
        } else if (currBoatStatus == 3) { //当前为“自动驾驶”状态
            sysBoat.setStatus(BoatStatus.AutoDriving.getCode());
            sysBoat.setStatusDesc(BoatStatus.AutoDriving.getInfo());
        }

        //更新channelInfo中的船只状态。
        channelInfo.setBoatStatus(currBoatStatus);
    }

    /**
     * 解析电子围栏报警状态。
     *
     * @param sysBoat
     * @param channelInfo
     * @param currFenceAlarmStatus 当前的电子围栏报警状态值
     */
    private void parseFenceAlarmStatus(SysBoat sysBoat, SocketChannelManager.ChannelInfo channelInfo, byte currFenceAlarmStatus) {
        if (Objects.isNull(sysBoat)) return;
        Long scenicId = sysBoat.getScenicId();

        //创建BoatAlarmInfo对象
        BoatAlarmInfo boatAlarmInfo = new BoatAlarmInfo();
        boatAlarmInfo.setBoatIdentityId(sysBoat.getBoatIdentityId());
        boatAlarmInfo.setBoatId(sysBoat.getBoatId());
        boatAlarmInfo.setBoatCode(sysBoat.getBoatCode());
        boatAlarmInfo.setScenicId(sysBoat.getScenicId());
        boatAlarmInfo.setType(AlarmType.Fence_Alarm); //设置报警类型。0：电子围栏，1：电机报警。
        boatAlarmInfo.setPhone("18707180041");

        IBoatAlarmInfoService boatAlarmInfoService;

        //获取上一次的电子围栏报警状态值
        byte previousFenceAlarmStatus = channelInfo.getFenceAlarmStatus();
        //从0到1才触发黄区报警，其他情况(比如从2到1)不要触发
        if (previousFenceAlarmStatus == 0 && currFenceAlarmStatus == 1) {
            //出现黄区报警
            log.info("电子围栏黄区报警");
            boatAlarmInfo.setLevel(AlarmLevel.Warn_Type);  //设置报警级别。0：普通，1：警告，2：紧急。
            boatAlarmInfo.setStatus(AlarmResolveStatus.Unresolve); //设置报警的处理状态。0：未处理，1：已消除。2，已处理
            boatAlarmInfo.setContent("电子围栏");

            boatAlarmInfoService = SpringUtils.getBean(IBoatAlarmInfoService.class);
            //将报警信息插入到数据库中保存
            if (Objects.nonNull(boatAlarmInfoService)) {
                boatAlarmInfoService.insertBoatAlarmInfo(boatAlarmInfo);
                previousAlarmInfoId = boatAlarmInfo.getAlarmInfoId();
            }

            //给景区监控中心发送船只报警状态信息
            WebSocketMessageHandler.sendMsgToScenicAdmin(scenicId, boatAlarmInfo);
        } else if (previousFenceAlarmStatus == 1 && currFenceAlarmStatus == 2) { //从1到2才触发红区报警，其他情况(比如从0到2)不要触发
            //出现红区报警
            log.info("电子围栏红区报警");
            boatAlarmInfo.setLevel(AlarmLevel.Urgent_Type);  //设置报警级别。0：普通，1：警告，2：紧急。
            boatAlarmInfo.setStatus(AlarmResolveStatus.Unresolve); //设置报警的处理状态。0：未处理，1：已消除。2，已处理
            boatAlarmInfo.setContent("电子围栏");

            boatAlarmInfoService = SpringUtils.getBean(IBoatAlarmInfoService.class);
            //将报警信息插入到数据库中保存
            if (Objects.nonNull(boatAlarmInfoService)) {
                boatAlarmInfoService.insertBoatAlarmInfo(boatAlarmInfo);
                previousAlarmInfoId = boatAlarmInfo.getAlarmInfoId();
            }
            //给景区监控中心发送船只报警状态信息
            WebSocketMessageHandler.sendMsgToScenicAdmin(scenicId, boatAlarmInfo);
        } else if (previousFenceAlarmStatus == 1 && currFenceAlarmStatus == 0) { //从1到0才触发解除黄区报警，其他情况(比如从2到0)不要触发
            //解除报警
            log.info("解除电子围栏黄区报警");

            boatAlarmInfoService = SpringUtils.getBean(IBoatAlarmInfoService.class);
            //从数据库中找出上一条报警信息对象，然后将其状态更新为“已消除”
            if (Objects.nonNull(boatAlarmInfoService) && Objects.nonNull(previousAlarmInfoId)) {
                BoatAlarmInfo existBoatAlarmInfo = boatAlarmInfoService.selectBoatAlarmInfoByAlarmInfoId(previousAlarmInfoId);
                if (Objects.nonNull(existBoatAlarmInfo)) {
                    existBoatAlarmInfo.setStatus(AlarmResolveStatus.Cancelled); //设置报警的处理状态。0：未处理，1：已消除。2，已处理
                    existBoatAlarmInfo.setContent("电子围栏");
                    boatAlarmInfoService.updateBoatAlarmInfo(existBoatAlarmInfo);
                }
            }

            //给景区监控中心发送船只报警状态信息
            WebSocketMessageHandler.sendMsgToScenicAdmin(scenicId, boatAlarmInfo);
        }

    }

    /**
     * 解析安全须知确认状态。
     * <p>
     * 安全须知确认状态与开锁和关锁状态对应关系：
     * 1，安全须知确认状态值从非1变为1即为开锁；
     * 2，安全须知确认状态值从非3变为3即为开锁；
     *
     * @param channel
     * @param channelInfo
     * @param currSafetyInstructionConfirStatus 当前最新的安全须知确认状态。
     */
    private void parseSafetyInstructionConfirStatus(Channel channel, SocketChannelManager.ChannelInfo channelInfo, byte currSafetyInstructionConfirStatus) {
        if (Objects.nonNull(channelInfo)) {
            //获取上一次的安全须知确认状态，然后与当前最新的安全须知确认状态进行对比。
            byte previousSafetyInstructionConfirmStatus = channelInfo.getSafetyInstructionConfirStatus();
            if (previousSafetyInstructionConfirmStatus != 1 && currSafetyInstructionConfirStatus == 1) { //接收到解锁成功的指令
                processAfterUnlock(channel);
            } else if (previousSafetyInstructionConfirmStatus != 3 && currSafetyInstructionConfirStatus == 3) { //接收到关锁成功的指令
                processAfterlock(channel);
            } else {
                //其他状态无需处理
            }

            //更新channelInfo对象的安全须知确认状态
            channelInfo.setSafetyInstructionConfirStatus(currSafetyInstructionConfirStatus);
        }
    }

    /**
     * 上锁成功后的处理方法。
     *
     * @param channel
     */
    private void processAfterlock(Channel channel) {
        //首先获取当前船只的注册id
        String boatIdentityId = SocketChannelManager.getSysBoatBySocketChannel(channel).getBoatIdentityId();
        log.info("船只{}接收到上锁成功的指令", boatIdentityId);

        //根据船只出厂id查找到订单状态为"使用中"的UserBoatOrderInfo对象。注意：需确保当前订单状态为使用中。
        UserBoatOrderInfo userBoatOrderInfo = userBoatOrderInfoService.getUserBoatOrderInfoOfUsingStatusByBoatIdentityId(boatIdentityId);
        if (Objects.nonNull(userBoatOrderInfo)) {
            //订单结束计费的时间
            Date endTime = new Date();

            int orderType = userBoatOrderInfo.getOrderType();
            String orderId = userBoatOrderInfo.getOrderId();
            Long userId = userBoatOrderInfo.getUserId();
            if (orderType == 0) { //当前使用中的订单为计时计费订单
                //查找到当前的订单对象
                ISysJishiOrderService sysJishiOrderService = SpringUtils.getBean(ISysJishiOrderService.class);
                SysJishiOrder usingJishiOrder = sysJishiOrderService.selectSysJishiOrderByOrderId(orderId);
                if (Objects.nonNull(usingJishiOrder)) {
                    Date beginTime = usingJishiOrder.getBeginTime();
                    //获取订单实际持续的收费时长，单位：毫秒。
                    long realDuration = endTime.getTime() - beginTime.getTime();
                    //计算计时计费订单的实际费用。
                    double orderFee = calculateJishiOrderFee(usingJishiOrder.getJishiId(), realDuration);

                    //将订单状态更新为“待付款”。注意：此处订单状态不能修改为“已完成”，因为此时订单费用还未支付，当前只是结束计费了，订单并未完成。
                    usingJishiOrder.setOrderStatus(OrderStatus.Unpaid.getCode());
                    usingJishiOrder.setOrderStatusDesc(OrderStatus.Unpaid.getInfo());

                    //设置订单停止计费的时间
                    usingJishiOrder.setEndTime(endTime);
                    //usingJishiOrder.setClosedTime(endTime); //这里不能设置订单关闭时间，因为此时订单还未支付费用。

                    //设置订单实际持续的收费时长
                    usingJishiOrder.setRealDuration(realDuration);
                    //设置订单费用金额
                    usingJishiOrder.setOrderFee(orderFee);
                    //更新计时计费订单
                    sysJishiOrderService.updateSysJishiOrder(usingJishiOrder);

                    ISysBoatService sysBoatService = SpringUtils.getBean(ISysBoatService.class);
                    //更新船只状态为“空闲”
                    sysBoatService.updateBoatStatusByBoadId(usingJishiOrder.getBoatId(), BoatStatus.Idle);

                    //删除船只订单关联对象(此时删除是合适的，因为此时用户，船只，订单三者之间的关联绑定关系已解除，尤其是用户与船只之间的绑定关系已解除，该船只从此刻开始可以被其他用户下单使用。)
                    userBoatOrderInfoService.deleteUserBoatOrderInfoByUserId(usingJishiOrder.getUserId());

                    //创建欲发送给小程序端的消息对象
                    JishiOrderEndChargeMsg jishiOrderEndChargeMsg = new JishiOrderEndChargeMsg();
                    jishiOrderEndChargeMsg.setOrderType(orderType);
                    jishiOrderEndChargeMsg.setOrderId(orderId);
                    jishiOrderEndChargeMsg.setBeginTime(beginTime);
                    jishiOrderEndChargeMsg.setEndTime(endTime);
                    jishiOrderEndChargeMsg.setRealDuration(realDuration);
                    jishiOrderEndChargeMsg.setOrderFee(orderFee);
                    //查找到当前订单所属小程序用户的WebSocketChannel
                    Channel miniProgramChannel = WebSocketChannelManager.getMiniprogramWebsocketChannelByUserId(userId);
                    if (Objects.nonNull(miniProgramChannel)) {
                        //通知小程序客户端停止当前计时计费订单的计费。
                        WebSocketMessageHandler.notifyClientSideJishiOrderStopCharging(miniProgramChannel, jishiOrderEndChargeMsg);
                    } else {
                        //如果此时发现当前订单所属用户不在线，则需要将还未发送出去的消息缓存下来，待该用户下次成功登录小程序后再主动给其发送此消息
                        log.warn("当前订单所属用户[userId={}]ws未连接，发送订单停止计费消息失败，将该消息存储起来待该用户下次连接后重发！", userId);
                        WebSocketChannelManager.addWsMsgBean(userId, jishiOrderEndChargeMsg);
                    }

                    //创建运营动态信息
                    ScenicOperationDynamicInfo scenicOperationDynamicInfo = new ScenicOperationDynamicInfo();
                    scenicOperationDynamicInfo.setOperationType(OperationType.Order_Closed);
                    scenicOperationDynamicInfo.setScenicId(usingJishiOrder.getScenicId());
                    scenicOperationDynamicInfo.setBoatId(usingJishiOrder.getBoatId());
                    scenicOperationDynamicInfo.setBoatCode(usingJishiOrder.getBoatCode());
                    scenicOperationDynamicInfo.setOrderId(orderId);
                    scenicOperationDynamicInfo.setOrderType(orderType);
                    scenicOperationDynamicInfo.setUserId(usingJishiOrder.getUserId());
                    Date createTime = new Date();
                    scenicOperationDynamicInfo.setCreateTime(createTime);
                    IScenicOperationDynamicInfoService operationInfoService = SpringUtils.getBean(IScenicOperationDynamicInfoService.class);
                    scenicOperationDynamicInfo.setContent(String.format("%s, %s船关锁成功，订单结束计费!", sdf.format(createTime), usingJishiOrder.getBoatCode()));
                    operationInfoService.insertOperationInfo(scenicOperationDynamicInfo);

                    //向景区监控中心发送运营动态信息
                    WebSocketMessageHandler.sendMsgToScenicAdmin(usingJishiOrder.getScenicId(), scenicOperationDynamicInfo);
                }

            } else if (orderType == 1) { //当前使用中的订单为套餐计费订单
                ISysTaocanOrderService sysTaocanOrderService = SpringUtils.getBean(ISysTaocanOrderService.class);
                SysTaocanOrder usingTaocanOrder = sysTaocanOrderService.selectSysTaocanOrderByOrderId(orderId);
                if (Objects.nonNull(usingTaocanOrder)) {
                    Date beginTime = usingTaocanOrder.getBeginTime();
                    //获取订单实际持续的收费时长，单位：毫秒。
                    long realDuration = endTime.getTime() - beginTime.getTime();
                    //套餐计费订单此时需要先判断实际计费时长是否查过当前套餐包含的时长，如果没有超则此时可以直接结束并关闭订单。如果超过了
                    // 套餐时长则需要计算超过的时长，然后依据当前景区计时计费的规则来计算超出时长应补交的费用。
                    //将实际使用时长realDuration(单位：毫秒)换算成分钟(且取整)，不足一分钟的给舍弃了
//                    int realMinute = (int) (realDuration / (60 * 1000));
                    //设置订单停止计费的时间
                    usingTaocanOrder.setEndTime(endTime);
                    //设置订单实际持续的收费时长
                    usingTaocanOrder.setRealDuration(realDuration);

                    ISysBoatService sysBoatService = SpringUtils.getBean(ISysBoatService.class);
                    //更新船只状态为“空闲”
                    sysBoatService.updateBoatStatusByBoadId(usingTaocanOrder.getBoatId(), BoatStatus.Idle);

                    //删除船只订单关联对象(此时删除是合适的，因为此时用户，船只，订单三者之间的关联绑定关系已解除，尤其是用户与船只之间的绑定关系已解除，该船只从此刻开始可以被其他用户下单使用。)
                    userBoatOrderInfoService.deleteUserBoatOrderInfoByUserId(usingTaocanOrder.getUserId());

                    //计算超时时长(单位：分钟)。注意：此处将毫秒值转为分钟，且不足一分钟的给舍弃掉了。
                    int overtime = (int) (realDuration - usingTaocanOrder.getTaocanDuration() * 60 * 1000) / (60 * 1000);
                    if (overtime <= 0) { //超时时长为负数即为未超时，超时时长为0即为超时不足一分钟，这两种情况都设置超时时长为0，且均不收取超时费。
                        overtime = 0;
                    }
                    usingTaocanOrder.setOvertime(overtime);

                    //计算超时费用(单位：元)
                    double overtimeFee = 0;
                    TaocanOrderEndChargeMsg taocanOrderEndChargeMsg = new TaocanOrderEndChargeMsg();
                    taocanOrderEndChargeMsg.setOrderId(orderId);
                    taocanOrderEndChargeMsg.setOrderType(orderType);
                    taocanOrderEndChargeMsg.setBeginTime(beginTime);
                    taocanOrderEndChargeMsg.setEndTime(endTime);
                    taocanOrderEndChargeMsg.setRealDuration(realDuration);
                    taocanOrderEndChargeMsg.setTaocanDuration(usingTaocanOrder.getTaocanDuration()); //原套餐时长
                    taocanOrderEndChargeMsg.setOvertime(overtime);
                    //查找到当前订单所属小程序用户的WebSocketChannel
                    Channel miniProgramChannel = WebSocketChannelManager.getMiniprogramWebsocketChannelByUserId(userId);
                    if (realDuration > 60 * 1000) { //订单实际总计费时长超过1分钟
                        if (overtime > 0) { //此时套餐订单超时了，且超过1分钟(含)以上
                            //将订单状态更新为“待付款”。注意：此时订单状态不能修改为“已完成”，因为此时订单超时费用还未支付，当前只是结束计费了，订单并未完成。
                            usingTaocanOrder.setOrderStatus(OrderStatus.Unpaid.getCode());
                            usingTaocanOrder.setOrderStatusDesc(OrderStatus.Unpaid.getInfo());

                            //计算需要补交的订单超时费用
                            //1,先获取当前景区的计时计费规则
//                            double price = usingTaocanOrder.getJishiPrice(); //计价：元/分钟
                            Double overtimeFeeRule = usingTaocanOrder.getOvertimeFeeRule(); //套餐超时计费规则，单位：元/分钟。
                            if (Objects.nonNull(overtimeFeeRule)) {
                                overtimeFee = overtimeFeeRule * overtime;
                            } else {
                                overtimeFee = 0;
                            }

                            //设置订单超时费用需支付金额及当前支付状态
                            usingTaocanOrder.setOvertimeFee(overtimeFee);
                            usingTaocanOrder.setOvertimeFeePayStatus(PayStatus.Unpaid.getCode());
                            usingTaocanOrder.setOvertimeFeePayStatusDesc(PayStatus.Unpaid.getInfo());

                            sysTaocanOrderService.updateSysTaocanOrder(usingTaocanOrder);

                            //通知小程序客户端订单停止计费。
                            taocanOrderEndChargeMsg.setOvertimeFee(overtimeFee);
                            if (Objects.nonNull(miniProgramChannel)) {
                                WebSocketMessageHandler.notifyClientSideTaocanOrderStopCharging(miniProgramChannel, taocanOrderEndChargeMsg);
                            } else {
                                //如果此时发现当前订单所属用户不在线，则需要将还未发送出去的消息缓存下来，待该用户下次成功登录小程序后再主动给其发送此消息
                                log.warn("当前订单所属用户[userId={}]ws未连接，发送订单停止计费消息失败，将该消息存储起来待该用户下次连接后重发！", userId);
                                WebSocketChannelManager.addWsMsgBean(userId, taocanOrderEndChargeMsg);
                            }

                        } else { //订单未超时或超时时长不足1分钟
                            //结束并关闭套餐订单。
                            finishTaocanOrder(usingTaocanOrder, taocanOrderEndChargeMsg, miniProgramChannel);
                        }
                    } else { //订单总计费时长不足1分钟(含)
                        //结束并关闭套餐订单。
                        finishTaocanOrder(usingTaocanOrder, taocanOrderEndChargeMsg, miniProgramChannel);
                    }

                    //创建运营动态信息
                    ScenicOperationDynamicInfo scenicOperationDynamicInfo = new ScenicOperationDynamicInfo();
                    scenicOperationDynamicInfo.setOperationType(OperationType.Order_Closed);
                    scenicOperationDynamicInfo.setScenicId(usingTaocanOrder.getScenicId());
                    scenicOperationDynamicInfo.setBoatId(usingTaocanOrder.getBoatId());
                    scenicOperationDynamicInfo.setBoatCode(usingTaocanOrder.getBoatCode());
                    scenicOperationDynamicInfo.setOrderId(orderId);
                    scenicOperationDynamicInfo.setOrderType(orderType);
                    scenicOperationDynamicInfo.setUserId(usingTaocanOrder.getUserId());
                    Date createTime = new Date();
                    scenicOperationDynamicInfo.setCreateTime(createTime);
                    IScenicOperationDynamicInfoService operationInfoService = SpringUtils.getBean(IScenicOperationDynamicInfoService.class);
                    scenicOperationDynamicInfo.setContent(String.format("%s, %s船关锁成功，订单结束计费!", sdf.format(createTime), usingTaocanOrder.getBoatCode()));
                    operationInfoService.insertOperationInfo(scenicOperationDynamicInfo);

                    //向景区监控中心发送运营动态信息
                    WebSocketMessageHandler.sendMsgToScenicAdmin(usingTaocanOrder.getScenicId(), scenicOperationDynamicInfo);

                }
            } else if (orderType == 2) { //当前使用中的订单为拼团订单

            }
        }
    }

    /**
     * 解锁成功后的处理方法。
     *
     * @param channel
     */
    private void processAfterUnlock(Channel channel) {
        //首先获取当前船只的注册id
        String boatIdentityId = SocketChannelManager.getSysBoatBySocketChannel(channel).getBoatIdentityId();
        log.info("船只{}接收到解锁成功的指令", boatIdentityId);

        //根据船只出厂id查找到订单状态为"使用中"的UserBoatOrderInfo对象。
        UserBoatOrderInfo userBoatOrderInfo = userBoatOrderInfoService.getUserBoatOrderInfoOfUsingStatusByBoatIdentityId(boatIdentityId);
        if (Objects.nonNull(userBoatOrderInfo)) {
            //订单开始计费的时间
            Date beginTime = new Date();

            int orderType = userBoatOrderInfo.getOrderType();
            String orderId = userBoatOrderInfo.getOrderId();
            Long userId = userBoatOrderInfo.getUserId();
            Long boatId = userBoatOrderInfo.getBoatId();
            if (orderType == 0) { //当前使用中的订单为计时计费订单
                //查找到当前的订单对象
                ISysJishiOrderService sysJishiOrderService = SpringUtils.getBean(ISysJishiOrderService.class);
                SysJishiOrder usingJishiOrder = sysJishiOrderService.selectSysJishiOrderByOrderId(orderId);
                if (Objects.nonNull(usingJishiOrder)) {
                    Date originBeginTime = usingJishiOrder.getBeginTime();
                    if (Objects.isNull(originBeginTime)) {
                        //设置订单开始计费的时间
                        usingJishiOrder.setBeginTime(beginTime);
                        sysJishiOrderService.updateSysJishiOrder(usingJishiOrder);

                        ISysBoatService sysBoatService = SpringUtils.getBean(ISysBoatService.class);
                        //更新船只状态为“自动驾驶中”
                        sysBoatService.updateBoatStatusByBoadId(boatId, BoatStatus.AutoDriving);

                        //创建计时计费订单开始计费的消息对象
                        OrderBeginChargeMsg orderBeginChargeMsg = new OrderBeginChargeMsg();
                        orderBeginChargeMsg.setOrderId(orderId);
                        orderBeginChargeMsg.setOrderType(orderType);
                        orderBeginChargeMsg.setBeginTime(beginTime);
                        //查找到当前订单所属小程序用户的WebSocketChannel
                        Channel miniProgramChannel = WebSocketChannelManager.getMiniprogramWebsocketChannelByUserId(userId);
                        if (Objects.nonNull(miniProgramChannel)) {
                            //通知小程序客户端当前计时计费订单开始计费。
                            WebSocketMessageHandler.notifyMiniClientOrderBeginCharging(miniProgramChannel, orderBeginChargeMsg);
                        } else {
                            //如果此时发现当前订单所属用户不在线，则需要将还未发送出去的消息缓存下来，待该用户下次成功登录小程序后再主动给其发送此消息
                            log.info("当前订单所属用户[userId={}]ws未连接，发送订单开始计费消息失败，将该消息存储起来待该用户下次连接后重发！", userId);
                            WebSocketChannelManager.addWsMsgBean(userId, orderBeginChargeMsg);
                        }

                        //创建运营动态信息
                        ScenicOperationDynamicInfo scenicOperationDynamicInfo = new ScenicOperationDynamicInfo();
                        scenicOperationDynamicInfo.setOperationType(OperationType.Order_Closed);
                        scenicOperationDynamicInfo.setScenicId(usingJishiOrder.getScenicId());
                        scenicOperationDynamicInfo.setBoatId(usingJishiOrder.getBoatId());
                        scenicOperationDynamicInfo.setBoatCode(usingJishiOrder.getBoatCode());
                        scenicOperationDynamicInfo.setOrderId(orderId);
                        scenicOperationDynamicInfo.setOrderType(orderType);
                        scenicOperationDynamicInfo.setUserId(usingJishiOrder.getUserId());
                        Date createTime = new Date();
                        scenicOperationDynamicInfo.setCreateTime(createTime);
                        IScenicOperationDynamicInfoService operationInfoService = SpringUtils.getBean(IScenicOperationDynamicInfoService.class);
                        scenicOperationDynamicInfo.setContent(String.format("%s, %s船开锁成功，订单开始计费!", sdf.format(createTime), usingJishiOrder.getBoatCode()));
                        operationInfoService.insertOperationInfo(scenicOperationDynamicInfo);

                        //向景区监控中心发送运营动态信息
                        WebSocketMessageHandler.sendMsgToScenicAdmin(usingJishiOrder.getScenicId(), scenicOperationDynamicInfo);
                    } else { //此时说明当前订单之前已经开始计时了,因此不能再次更新订单开始时间，也不能通知小程序开始计费
                    }
                }
            } else if (orderType == 1) { //当前使用中的订单为套餐订单
                ISysTaocanOrderService sysTaocanOrderService = SpringUtils.getBean(ISysTaocanOrderService.class);
                SysTaocanOrder usingTaocanOrder = sysTaocanOrderService.selectSysTaocanOrderByOrderId(orderId);
                if (Objects.nonNull(usingTaocanOrder)) {
                    Date originBeginTime = usingTaocanOrder.getBeginTime();
                    if (Objects.isNull(originBeginTime)) {
                        //设置订单开始计费的时间
                        usingTaocanOrder.setBeginTime(beginTime);
                        sysTaocanOrderService.updateSysTaocanOrder(usingTaocanOrder);

                        ISysBoatService sysBoatService = SpringUtils.getBean(ISysBoatService.class);
                        //更新船只状态为“自动驾驶中”
                        sysBoatService.updateBoatStatusByBoadId(boatId, BoatStatus.AutoDriving);

                        //通知小程序客户端订单开始计费
                        OrderBeginChargeMsg orderBeginChargeMsg = new OrderBeginChargeMsg();
                        orderBeginChargeMsg.setOrderId(orderId);
                        orderBeginChargeMsg.setOrderType(orderType);
                        orderBeginChargeMsg.setBeginTime(beginTime);
                        //查找到当前订单所属小程序用户的WebSocketChannel
                        Channel miniProgramChannel = WebSocketChannelManager.getMiniprogramWebsocketChannelByUserId(userId);
                        if (Objects.nonNull(miniProgramChannel)) {
                            //通知小程序客户端当前计时计费订单开始计费。
                            WebSocketMessageHandler.notifyMiniClientOrderBeginCharging(miniProgramChannel, orderBeginChargeMsg);
                        } else {
                            //如果此时发现当前订单所属用户不在线，则需要将还未发送出去的消息缓存下来，待该用户下次成功登录小程序后再主动给其发送此消息
                            log.info("当前订单所属用户[userId={}]ws未连接，发送订单开始计费消息失败，将该消息存储起来待该用户下次连接后重发！", userId);
                            WebSocketChannelManager.addWsMsgBean(userId, orderBeginChargeMsg);
                        }

                        //创建运营动态信息
                        ScenicOperationDynamicInfo scenicOperationDynamicInfo = new ScenicOperationDynamicInfo();
                        scenicOperationDynamicInfo.setOperationType(OperationType.Order_Closed);
                        scenicOperationDynamicInfo.setScenicId(usingTaocanOrder.getScenicId());
                        scenicOperationDynamicInfo.setBoatId(usingTaocanOrder.getBoatId());
                        scenicOperationDynamicInfo.setBoatCode(usingTaocanOrder.getBoatCode());
                        scenicOperationDynamicInfo.setOrderId(orderId);
                        scenicOperationDynamicInfo.setOrderType(orderType);
                        scenicOperationDynamicInfo.setUserId(usingTaocanOrder.getUserId());
                        Date createTime = new Date();
                        scenicOperationDynamicInfo.setCreateTime(createTime);
                        IScenicOperationDynamicInfoService operationInfoService = SpringUtils.getBean(IScenicOperationDynamicInfoService.class);
                        scenicOperationDynamicInfo.setContent(String.format("%s, %s船开锁成功，订单开始计费!", sdf.format(createTime), usingTaocanOrder.getBoatCode()));
                        operationInfoService.insertOperationInfo(scenicOperationDynamicInfo);

                        //向景区监控中心发送运营动态信息
                        WebSocketMessageHandler.sendMsgToScenicAdmin(usingTaocanOrder.getScenicId(), scenicOperationDynamicInfo);
                    } else { //此时说明当前订单之前已经开始计时了,因此不能再次更新订单开始时间，也不能通知小程序开始计费
                    }
                }
            } else if (orderType == 2) { //当前订单为拼团订单
                String parentOrderId = userBoatOrderInfo.getOrderId(); //获取拼团父订单id
                ISysPintuanParentOrderService sysPintuanParentOrderService = SpringUtils.getBean(ISysPintuanParentOrderService.class);
                ISysPintuanChildrenOrderService sysPintuanChildrenOrderService = SpringUtils.getBean(ISysPintuanChildrenOrderService.class);
                SysPintuanParentOrder sysPintuanParentOrder = sysPintuanParentOrderService.selectSysPintuanParentOrderByOrderId(parentOrderId);
                List<SysPintuanChildrenOrder> sysPintuanChildrenOrders = sysPintuanParentOrder.getPintuanChildrenOrderList();

                //更新拼团父订单sysPintuanParentOrder
                //将订单状态更新为“使用中”
                sysPintuanParentOrder.setOrderStatus(OrderStatus.Using.getCode());
                sysPintuanParentOrder.setOrderStatusDesc(OrderStatus.Using.getInfo());

                //通知小程序客户端订单开始计费
                OrderBeginChargeMsg orderBeginChargeMsg = new OrderBeginChargeMsg();
                orderBeginChargeMsg.setOrderType(orderType);
                orderBeginChargeMsg.setBeginTime(beginTime);

                sysPintuanChildrenOrders.forEach(childOrder -> {
                    Channel miniProgramChannel = WebSocketChannelManager.getMiniprogramWebsocketChannelByUserId(childOrder.getUserId());
                    if (Objects.nonNull(miniProgramChannel)) {
                        //将订单状态更新为“使用中”
                        childOrder.setOrderStatus(OrderStatus.Using.getCode());
                        childOrder.setOrderStatusDesc(OrderStatus.Using.getInfo());
                        //设置订单开始计费的时间
                        childOrder.setBeginTime(beginTime);
                        sysPintuanChildrenOrderService.updateSysPintuanChildrenOrder(childOrder);

                        orderBeginChargeMsg.setOrderId(childOrder.getOrderId());
                        WebSocketMessageHandler.notifyMiniClientOrderBeginCharging(miniProgramChannel, orderBeginChargeMsg);
                    }
                });
            }

            //更新船只状态为“空闲”
            ISysBoatService sysBoatService = SpringUtils.getBean(ISysBoatService.class);
            SysBoat sysBoat = new SysBoat();
            sysBoat.setBoatId(boatId);
            sysBoat.setStatus(BoatStatus.ManualDriving.getCode());
            sysBoat.setStatusDesc(BoatStatus.ManualDriving.getInfo());
            sysBoatService.updateSysBoat(sysBoat);
        }
    }


    /**
     * 解析上锁状态。
     *
     * @param channel
     * @param channelInfo
     * @param lockStatus
     */
//    private void parselockStatus(Channel channel, SocketChannelManager.ChannelInfo channelInfo, byte lockStatus) {
//        if (Objects.nonNull(channelInfo)) {
//            byte previousLockStatus = channelInfo.getLockStatus();
//            if (previousLockStatus != 1 && lockStatus == 1) { //首次接收到上锁成功的指令
//                processAfterUnlock(channel);
//            }
//            channelInfo.setLockStatus(lockStatus);
//        }
//    }

    /**
     * 解析解锁状态。
     *
     * @param channel
     * @param channelInfo
     * @param currUnlockStatus
     */
//    private void parseUnlockStatus(Channel channel, SocketChannelManager.ChannelInfo channelInfo,
//                                   byte currUnlockStatus) {
//        if (Objects.nonNull(channelInfo)) {
//            byte previousUnlockStatus = channelInfo.getUnlockStatus();
//            if (previousUnlockStatus != 1 && currUnlockStatus == 1) { //首次接收到解锁成功的指令
//                processAfterlock(channel);
//            }
//            channelInfo.setUnlockStatus(currUnlockStatus);
//        }
//    }

    /**
     * 发送船只状态信息报文。
     */
    public void sendStatusInfoMessage(Channel channel, BoatRealtimeStatusInfo boatRealtimeStatusInfo) {
        if (Objects.isNull(channel) || Objects.isNull(boatRealtimeStatusInfo)) return;
        BoatMessage boatMessage = new BoatMessage(MessageType.STATUS_MESSAGE, (byte) 1);
        List<BaseDataPiece> dataPieceList = boatMessage.getDataPieceList();

//        NavigationInfo navigationInfo = new NavigationInfo();


        channel.writeAndFlush(boatMessage);
    }

    /**
     * @param paopaotunBoatStatus 泡泡豚状态报文中的船只状态。0：无操作，1：空闲，2：手动，3：自动，4：离线，5：维护，6-14：保留，15：无效值
     * @return 系统船只状态。0=离线,1=维护,2=空闲,3=支付倒计时中,4=拼团中,5=上船倒计时中,6=开锁等待中,7=手动驾驶中,8=自动驾驶中。
     */
    private byte translatePaopaotunBoatStatusToRealtimeBoatStatus(byte paopaotunBoatStatus) {
        byte boatStatus;
        if (paopaotunBoatStatus == 1) { //空闲
            boatStatus = 2;
        } else if (paopaotunBoatStatus == 2) { //手动
            boatStatus = 7;
        } else if (paopaotunBoatStatus == 3) { //自动
            boatStatus = 8;
        } else {
            boatStatus = 2;
        }
        return boatStatus;
    }

    /**
     * 计算计时计费订单的实际费用。
     *
     * @param jishiId
     * @param realDuration
     * @return
     */
    public static double calculateJishiOrderFee(int jishiId, long realDuration) {
        ISysJishiService sysJishiService = SpringUtils.getBean(ISysJishiService.class);
        if (Objects.isNull(sysJishiService)) return 0;

        SysJishi sysJishi = sysJishiService.selectSysJishiByJishiId(jishiId);
        if (Objects.isNull(sysJishi)) return 0;

        //价格，单位：元。
        double price = sysJishi.getPrice();
        //计价单位，默认为"0"。(0: 元/分钟, 1: 元/30分钟, 2: 元/小时)
        int unit = sysJishi.getUnit();
//        BigDecimal timeDecimal = null;
//        if (unit == 0) {
//            //将时长转换为以分钟为单位，保留2为小数。结果示例：30.45分钟
//            timeDecimal = (new BigDecimal(realDuration)).divide(new BigDecimal(60 * 1000), 2, BigDecimal.ROUND_UP);
////                                            BigDecimal minuteDecimal = BigDecimal.valueOf(Double.parseDouble(String.valueOf(realDuration)) / (60 * 1000)).setScale(2, BigDecimal.ROUND_UP);
//        } else if (unit == 1) {
//            //将时长转换为以30分钟为单位，保留2为小数。结果示例：30.45 (30分钟)，即30.45个30分钟
//            timeDecimal = (new BigDecimal(realDuration)).divide(new BigDecimal(30 * 60 * 1000), 2, BigDecimal.ROUND_UP);
//        } else if (unit == 2) {
//            //将时长转换为以30分钟为单位，保留2为小数。结果示例：30.45 (30分钟)，即30.45个30分钟
//            timeDecimal = (new BigDecimal(realDuration)).divide(new BigDecimal(60 * 60 * 1000), 2, BigDecimal.ROUND_UP);
//        }

        //将实际持续时长从毫秒值转为分钟，如果值为0则不满一分钟，无需计费。
        int realMinute = (int) (realDuration / (60 * 1000));
        //算出订单费用，保留2为小数。费用=时长*价格
        BigDecimal consumeDecimal = new BigDecimal(realMinute).multiply(new BigDecimal(price)).setScale(2, BigDecimal.ROUND_UP);
        //计算出实际订单费用
        return consumeDecimal.doubleValue();
    }

    /**
     * 结束并关闭套餐订单。
     *
     * @param taocanOrder
     */
    public static void finishTaocanOrder(SysTaocanOrder taocanOrder, TaocanOrderEndChargeMsg taocanOrderEndChargeMsg, Channel
            miniProgramChannel) {
        //结束并关闭订单
        taocanOrder.setOrderStatus(OrderStatus.Fulfilled.getCode());
        taocanOrder.setOrderStatusDesc(OrderStatus.Fulfilled.getInfo());
        taocanOrder.setClosedTime(new Date());

        ISysTaocanOrderService sysTaocanOrderService = SpringUtils.getBean(ISysTaocanOrderService.class);
        //更新套餐计费订单
        sysTaocanOrderService.updateSysTaocanOrder(taocanOrder);

        //此时需要支付的订单超时费用为0
        taocanOrderEndChargeMsg.setOvertimeFee(0);

        //通知小程序客户端订单停止计费。
        if (Objects.nonNull(miniProgramChannel)) {
            WebSocketMessageHandler.notifyClientSideTaocanOrderStopCharging(miniProgramChannel, taocanOrderEndChargeMsg);
        } else {
            Long userId = taocanOrder.getUserId();
            //如果此时发现当前订单所属用户不在线，则需要将还未发送出去的消息缓存下来，待该用户下次成功登录小程序后再主动给其发送此消息
            log.warn("当前订单所属用户[userId={}]ws未连接，发送订单停止计费消息失败，将该消息存储起来待该用户下次连接后重发！", userId);
            WebSocketChannelManager.addWsMsgBean(userId, taocanOrderEndChargeMsg);
        }

        //更新船只状态为“空闲”
        ISysBoatService sysBoatService = SpringUtils.getBean(ISysBoatService.class);
        SysBoat sysBoat = new SysBoat();
        sysBoat.setBoatId(taocanOrder.getBoatId());
        sysBoat.setStatus(BoatStatus.Idle.getCode());
        sysBoat.setStatusDesc(BoatStatus.Idle.getInfo());
        sysBoatService.updateSysBoat(sysBoat);

        //订单已完成，退还押金
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                IWxPayV3Service wxPayV3Service = SpringUtils.getBean(IWxPayV3Service.class);
                if (Objects.nonNull(wxPayV3Service)) {
                    wxPayV3Service.refund(new WxRefundReqBody(taocanOrder.getOrderId(), 1, 0, "订单已完成，退还押金"));
                }
            }
        }, 5000);
    }


}
